//+------------------------------------------------------------------+
//|                                                  ExpertMoney.mqh |
//|                             Copyright 2000-2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#include "ExpertBase.h"  // EA基础类（提供品种、账户、价格序列等通用能力）

//+------------------------------------------------------------------+
//| CExpertMoney 类：EA 资金管理核心类                                |
//| 核心定位：继承自 CExpertBase，封装交易手数计算、风险控制逻辑，     |
//|           基于账户余额与风险率动态计算开仓手数，避免过度交易，     |
//|           同时提供持仓反转、强制平仓的手数决策支持                 |
//| 核心逻辑：以“固定风险率”为核心，通过 `m_percent` 参数控制单次交易 |
//|           允许承担的最大风险（占账户余额的百分比），计算安全手数   |
//| 适用场景：所有需要风险控制的 EA 策略，尤其适合追求稳定收益、       |
//|           严格控制回撤的交易系统（如趋势跟踪、波段交易）           |
//+------------------------------------------------------------------+
class CExpertMoney : public CExpertBase
  {
protected:
   //----------------------------------------------------------------
   // 核心风险参数（保护成员，通过公有方法设置）
   //----------------------------------------------------------------
   double            m_percent;  // 单次交易风险率（占账户余额的百分比，默认10.0%）
                                 // 取值范围：0.0~100.0，0.0表示不控制风险（禁止开仓）

public:
   //----------------------------------------------------------------
   // 构造与析构函数
   //----------------------------------------------------------------
   /**
    * @brief 构造函数：初始化资金管理对象
    * @details 1. 调用父类 CExpertBase 构造函数，初始化通用能力（品种、账户等）；
    *          2. 设置默认风险率为10.0%（单次交易最大允许亏损为账户余额的10%）；
    *          3. 无需额外资源初始化，依赖父类完成基础配置。
    */
                     CExpertMoney(void);

   /**
    * @brief 析构函数：清理资金管理对象资源
    * @details 1. 调用父类 CExpertBase 析构函数，释放通用资源；
    *          2. 无自定义动态资源，无需额外清理逻辑，避免内存泄漏。
    */
                    ~CExpertMoney(void);

   //----------------------------------------------------------------
   // 风险参数配置接口（外部调用，设置单次交易风险率）
   //----------------------------------------------------------------
   /**
    * @brief 设置单次交易风险率（核心参数，控制开仓手数的关键）
    * @param percent [in] 风险率（占账户余额的百分比，范围：0.0~100.0）
    * @note 1. 0.0%：禁止开仓（风险率为0，计算出的手数为0）；
    *       2. 1.0%~10.0%：适合稳健策略，控制单次亏损；
    *       3. 超过10%：高风险配置，可能导致账户快速回撤，需谨慎使用；
    *       4. 参数有效性需通过 ValidationSettings() 验证。
    */
   void              Percent(double percent) { m_percent = percent; }

   //----------------------------------------------------------------
   // 参数验证：确保风险参数合法，避免无效配置
   //----------------------------------------------------------------
   /**
    * @brief 验证资金管理参数的有效性（继承自父类，重写扩展）
    * @return bool - 验证结果：true=参数合法，false=参数无效
    * @note 验证逻辑：
    *       1. 先调用父类 CExpertBase::ValidationSettings() 验证基础配置（品种、周期等）；
    *       2. 再验证风险率 m_percent：必须在 0.0~100.0 范围内（含边界值）；
    *       3. 若风险率超出范围，输出错误日志并返回 false，阻止 EA 运行。
    */
   virtual bool      ValidationSettings();

   //----------------------------------------------------------------
   // 手数计算核心接口：为不同交易场景提供安全手数
   //----------------------------------------------------------------
   /**
    * @brief 计算多单（买入）的安全开仓手数
    * @param price [in] 开仓价格（0.0 表示使用当前市场价格：卖价 Bid）
    * @param sl [in] 止损价格（当前版本暂未直接使用，预留扩展接口）
    * @return double - 安全开仓手数（0.0 表示无法开仓，≥品种最小手数表示可开仓）
    * @note 计算逻辑：
    *       1. 检查品种对象是否有效（m_symbol 不为 NULL），无效则返回 0.0；
    *       2. 调用账户对象 m_account 的 MaxLotCheck() 方法，基于风险率计算最大允许手数：
    *          - 若 price=0.0：使用当前卖价（Bid）作为开仓价计算；
    *          - 若 price≠0.0：使用指定价格作为开仓价计算；
    *       3. 对比计算出的手数与品种最小手数（m_symbol.LotsMin()）：
    *          - 若计算手数 ≥ 最小手数：返回最小手数（避免手数过小，符合平台规则）；
    *          - 若计算手数 < 最小手数：返回 0.0（资金不足或风险率过低，无法开仓）。
    */
   virtual double    CheckOpenLong(double price, double sl);

   /**
    * @brief 计算空单（卖出）的安全开仓手数
    * @param price [in] 开仓价格（0.0 表示使用当前市场价格：买价 Ask）
    * @param sl [in] 止损价格（当前版本暂未直接使用，预留扩展接口）
    * @return double - 安全开仓手数（0.0 表示无法开仓，≥品种最小手数表示可开仓）
    * @note 计算逻辑与 CheckOpenLong() 一致，仅订单类型差异：
    *       1. 调用 MaxLotCheck() 时指定订单类型为 ORDER_TYPE_SELL（空单）；
    *       2. 若 price=0.0：使用当前买价（Ask）作为开仓价计算；
    *       3. 其他逻辑（手数对比、有效性检查）与多单完全相同。
    */
   virtual double    CheckOpenShort(double price, double sl);

   /**
    * @brief 计算持仓反转的安全手数（平仓当前持仓 + 开仓反向持仓）
    * @param position [in] 当前持仓信息对象（包含持仓类型、手数等）
    * @param sl [in] 新持仓的止损价格（传递给反向开仓手数计算接口）
    * @return double - 反转总手数（=当前持仓手数 + 反向开仓手数，0.0 表示无法反转）
    * @note 反转逻辑：
    *       1. 若当前持仓为多单（POSITION_TYPE_BUY）：计算空单开仓手数（CheckOpenShort()）；
    *       2. 若当前持仓为空单（POSITION_TYPE_SELL）：计算多单开仓手数（CheckOpenLong()）；
    *       3. 反转总手数 = 当前持仓手数（用于平仓） + 反向开仓手数（用于新持仓）；
    *       4. 若反向开仓手数为 0.0，总手数=当前持仓手数（仅平仓，不反转）。
    */
   virtual double    CheckReverse(CPositionInfo *position, double sl);

   /**
    * @brief 计算强制平仓的手数（当持仓亏损达到风险阈值时）
    * @param position [in] 当前持仓信息对象（包含持仓盈亏、手数等）
    * @return double - 平仓手数（0.0 表示无需平仓，=持仓手数表示需全平）
    * @note 强制平仓逻辑（核心风险控制）：
    *       1. 若风险率 m_percent=0.0：返回 0.0（不控制风险，无需平仓）；
    *       2. 计算持仓当前亏损：-position.Profit()（亏损为正，盈利为负）；
    *       3. 计算风险阈值：账户余额 × 风险率 ÷ 100.0（单次交易允许的最大亏损）；
    *       4. 若持仓亏损 > 风险阈值：返回持仓手数（需全平，控制亏损扩大）；
    *       5. 若持仓亏损 ≤ 风险阈值：返回 0.0（无需平仓，继续持有）。
    */
   virtual double    CheckClose(CPositionInfo *position);
  };
//+------------------------------------------------------------------+